package com.xiu.travel.security.service.impl;

import com.xiu.travel.constant.MessageConstant;
import com.xiu.travel.entity.UmsMember;
import com.xiu.travel.exception.Asserts;
import com.xiu.travel.other.UmsMemberConvertor;
import com.xiu.travel.other.UmsMemberDTO;
import com.xiu.travel.other.UmsMemberVO;
import com.xiu.travel.security.mapper.SysMenuMapper;
import com.xiu.travel.security.mapper.SysRoleMapper;
import com.xiu.travel.security.mapper.UmsMemberMapper;
import com.xiu.travel.security.pojo.SecurityUser;
import com.xiu.travel.security.service.UmsMemberService;
import com.xiu.travel.security.service.WeChatLoginService;
import com.xiu.travel.util.*;
import com.xiu.travel.util.api.CommonResult;
import com.xiu.travel.util.api.ResultCode;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.xiaoymin.knife4j.core.util.StrUtil;
import com.xiu.travel.util.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 会员表 服务实现类
 * </p>
 *
 * @author YuLing
 * @since 2023-03-15
 */
@Service
@Slf4j
public class UmsMemberServiceImpl extends ServiceImpl<UmsMemberMapper, UmsMember> implements UmsMemberService {

    @Value("${wechat.appid[1]}")
    private String BACKERAPPID;
    @Value("${wechat.appid[2]}")
    private String ZJHAPPID;
    @Value("${wechat.secret}")
    private String BACKERSECRET;

    @Value("${wechat.zjh-secret}")
    private String ZJHSECRET;
    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private WeChatLoginService weChatLoginService;

    @Autowired
    private UmsMemberMapper umsMemberMapper;
    @Autowired
    private SysMenuMapper sysMenuMapper;
    @Autowired
    private SysRoleMapper sysRoleMapper;

    public static String generateRandomString(String characters, int length) {
        StringBuilder sb = new StringBuilder(length);
        Random random = new Random();

        for (int i = 0; i < length; i++) {
            int index = random.nextInt(characters.length());
            char randomChar = characters.charAt(index);
            sb.append(randomChar);
        }

        return sb.toString();
    }

    /**
     * 保存
     *
     * @param dto 参数
     * @return 保存结果
     */
    @Override
    public boolean saveOne(UmsMemberDTO dto) {
        return this.save(UmsMemberConvertor.INSTANCE.toEntity(dto));
    }

    /**
     * 修改
     *
     * @param dto 参数
     * @return 修改结果
     */
    @Override
    public boolean updateById(UmsMemberDTO dto) {
        return this.updateById(UmsMemberConvertor.INSTANCE.toEntity(dto));
    }

    /**
     * 根据主键查询VO
     *
     * @param id 主键`
     * @return VO
     */
    @Override
    public UmsMemberVO getByPk(Long id) {
        UmsMember domain = this.getById(id);
        if (Objects.isNull(domain)) {
            Asserts.fail(ResultCode.FIND_NOT_RESULT);
        }
        return UmsMemberConvertor.INSTANCE.toVO(domain);
    }

    /**
     * 根据主键删除
     *
     * @param id) 主键
     * @return 删除结果
     */
    @Override
    public Boolean deleteByPk(Long id) {
        return this.removeById(id);
    }

    /**
     * 支持分页的dto条件查询
     *
     * @param page  分页组件
     * @param param 查询参数
     * @return IPage
     */
    @Override
    public IPage<UmsMemberVO> selectPageByDto(IPage<UmsMember> page, UmsMemberDTO param) {
        QueryWrapper<UmsMember> queryWrapper = Wrappers.query();
        IPage<UmsMember> iPage = this.getBaseMapper().selectPage(page, queryWrapper);
        return iPage.convert(UmsMemberConvertor.INSTANCE::toVO);
    }

    @Override
    public CommonResult<String> loadUserByUsername(String username, String password) throws UsernameNotFoundException {

        try {
            if (RSAUtils.isRsaEncrypted(password, RSAUtils.getPrivateKeyFromString(RSAUtils.privateKey))) {
                byte[] decrypt =
                        RSAUtils.decrypt(Base64.getDecoder().decode(password), RSAUtils.getPrivateKeyFromString(RSAUtils.privateKey));
                password = new String(decrypt);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }


        log.info("loadUserByUsername:{}==={}", username, password);
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                new UsernamePasswordAuthenticationToken(username, password);
        log.info("usernamePasswordAuthenticationToken:{}==={}", usernamePasswordAuthenticationToken, password);
        try {
            Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
            log.info("authenticate:{}==={}", authenticate, password);

            if (Objects.isNull(authenticate)) {
                throw new RuntimeException("登陆失败");
            }
//      认证通过 生成jwt返回
            SecurityUser securityUser = (SecurityUser) authenticate.getPrincipal();
            securityUser.setId(securityUser.getId());
            return CommonResult.success(JwtUtil.createJWT(JSON.toJSON(securityUser).toString()));
        } catch (Exception e) {
            e.printStackTrace();
            return CommonResult.failed(e.getMessage());
        }
    }

    public CommonResult<String> loadUserByMail(String mail, String password) throws UsernameNotFoundException {

        UmsMember umsMember = umsMemberMapper.selectOne(new LambdaQueryWrapper<UmsMember>().eq(UmsMember::getMail, mail));
        if (Objects.equals(umsMember.getPassword(), password)) {
            List<String> perms = Objects.requireNonNullElse(sysMenuMapper.selectPermsByUserId(umsMember.getId()), Collections.emptyList());
            List<String> roles = Objects.requireNonNullElse(sysRoleMapper.selectRoleByUserId(umsMember.getId()), Collections.emptyList());
            return CommonResult.success(JwtUtil.createJWT(JSON.toJSON(new SecurityUser(umsMember, perms, roles)).toString()));
        } else {
            return CommonResult.failed("密码错误");
        }

    }

    @Override
    public CommonResult<String> loginOut() {
        SecurityUser securityUser = SecurityUtils.getLoginUser();
        log.info("SecurityUser:{}", securityUser);
        Long userid = securityUser.getId();
        redisUtil.deleteObject("login:" + userid);
        return CommonResult.success("退出成功");
    }

    @Override
    //todo
    public CommonResult<String> loginBySms(String phone, String code, String password) {
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String username = UUID.randomUUID().toString();
        UmsMember newUmsMember =
                UmsMember.builder().phone(phone).password(passwordEncoder.encode(password)).username(username).build();
        //获取用户
        UmsMember umsMember = this.getOne(new LambdaQueryWrapper<UmsMember>().eq(UmsMember::getPhone, phone));

        if (ObjectUtils.isNotEmpty(umsMember) && !umsMember.getPassword().equals(newUmsMember.getPassword())) {
            Asserts.fail(ResultCode.BAD_USER_PASSWORD);
        }
        log.info("umsMember:{}", umsMember);
        //预留666666短信验证码测试
        if ("666666".equals(code)) {
            if (ObjectUtils.isEmpty(umsMember)) {
                this.save(newUmsMember);
            } else {
                username = umsMember.getUsername();
            }

        } else {

            String correctCode = redisUtil.getCacheObject("SMS:" + code);
            if (StrUtil.isBlank(correctCode) || !code.equals(correctCode)) {
                Asserts.fail(ResultCode.FIND_NOT_RESULT);
            }

            if (!ObjectUtils.isEmpty(umsMember) || this.save(newUmsMember)) {
                redisUtil.deleteObject("SMS" + code);
            }


        }
        return loadUserByUsername(username, password);
    }

    @Override
    public CommonResult<String> loginByWeChatToZJH(String openIdCode, String phoneCode) throws Exception {
        // 1.接收小程序发送的code
        // 2.开发者服务器 登录凭证校验接口 appi + appsecret + code
        com.alibaba.fastjson.JSONObject sessionKeyOpenId = this.getSessionKeyOpenId(openIdCode, ZJHAPPID, ZJHSECRET);
        // 3.接收微信接口服务 获取返回的参数
        String openid = sessionKeyOpenId.getString("openid");
        String sessionKey = sessionKeyOpenId.getString("session_key");
        String accessToken = weChatLoginService.getZJHAccessToken();
        JSONObject phoneInfo = JSONObject.parseObject(this.getPhoneByWeChatCode(phoneCode, accessToken).get("phone_info").toString());
        String purePhoneNumber = phoneInfo.get("purePhoneNumber").toString();

        UmsMember umsMember = this.getBaseMapper().selectOne(new LambdaQueryWrapper<UmsMember>()
                .eq(UmsMember::getOpenId, openid));
        if (umsMember == null) {
            umsMember = this.getBaseMapper().selectOne(new LambdaQueryWrapper<UmsMember>()
                    .eq(UmsMember::getPhone, purePhoneNumber));
            if (umsMember == null) {
                umsMember = new UmsMember();
                umsMember.setUsername(purePhoneNumber);
                umsMember.setPassword(null);
                umsMember.setNickname("宅家欢");
                umsMember.setOpenId(openid);
                umsMember.setPhone(purePhoneNumber);
                this.save(umsMember);
                //添加用户钱包
//                UmsWallet umsWallet=new UmsWallet()
//                        .setUserId(umsMember.getId())
//                        .setBalance(new BigDecimal(0));
//                umsWalletService.save(umsWallet);
            } else {
                umsMember.setOpenId(openid);
                this.getBaseMapper().updateById(umsMember);
            }
        }
        SecurityUser securityUser = getSecurityUserByUmsMember(umsMember);
        return CommonResult.success(JwtUtil.createJWT(JSON.toJSON(securityUser).toString()));
    }

    @Override
    public CommonResult<String> loginByWeChat(String openIdCode, String phoneCode) {
        // 1.接收小程序发送的code
        // 2.开发者服务器 登录凭证校验接口 appi + appsecret + code
        com.alibaba.fastjson.JSONObject sessionKeyOpenId = this.getSessionKeyOpenId(openIdCode, BACKERAPPID, BACKERSECRET);
        // 3.接收微信接口服务 获取返回的参数
        String openid = sessionKeyOpenId.getString("openid");
        String sessionKey = sessionKeyOpenId.getString("session_key");
        String accessToken = weChatLoginService.getAccessToken();
        JSONObject phoneInfo = JSONObject.parseObject(this.getPhoneByWeChatCode(phoneCode, accessToken).get("phone_info").toString());
        String purePhoneNumber = phoneInfo.get("purePhoneNumber").toString();

        UmsMember umsMember = this.getBaseMapper().selectOne(new LambdaQueryWrapper<UmsMember>()
                .eq(UmsMember::getOpenId, openid));
        if (umsMember == null) {
            umsMember = this.getBaseMapper().selectOne(new LambdaQueryWrapper<UmsMember>()
                    .eq(UmsMember::getPhone, purePhoneNumber));
            if (umsMember == null) {
                return CommonResult.failed(MessageConstant.PERMISSION_DENIED);
            } else {
                umsMember.setOpenId(openid);
                this.getBaseMapper().updateById(umsMember);
            }
        }
        if (!purePhoneNumber.equals(umsMember.getPhone())) {
            return CommonResult.failed(MessageConstant.PERMISSION_DENIED);
        }
        SecurityUser securityUser = getSecurityUserByUmsMember(umsMember);
        return CommonResult.success(JwtUtil.createJWT(JSON.toJSON(securityUser).toString()));
    }

    public SecurityUser getSecurityUserByUmsMember(UmsMember umsMember) {
        List<String> perms = sysMenuMapper.selectPermsByUserId(umsMember.getId());
        List<String> roles = sysRoleMapper.selectRoleByUserId(umsMember.getId());
        perms = Objects.requireNonNullElse(perms, Collections.emptyList());
        roles = Objects.requireNonNullElse(roles, Collections.emptyList());
        return new SecurityUser(umsMember, perms, roles);
    }

    private com.alibaba.fastjson.JSONObject getSessionKeyOpenId(String jsCode, String appId, String backerSecret) {
        String requestUrl = "https://api.weixin.qq.com/sns/jscode2session";
        Map<String, String> requestUrlParam = new HashMap<>();
        requestUrlParam.put("appid", appId);
        requestUrlParam.put("secret", backerSecret);
        requestUrlParam.put("js_code", jsCode);
        requestUrlParam.put("grant_type", "authorization_code");
        com.alibaba.fastjson.JSONObject
                sessionKeyOpenId =
                JSON.parseObject(HttpClientUtil.doPost(requestUrl, requestUrlParam));
        log.info("微信登录返回结果:{}", sessionKeyOpenId);
        return sessionKeyOpenId;
    }

    private com.alibaba.fastjson.JSONObject getPhoneByWeChatCode(String code, String accessToken) {
        String requestUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + accessToken;
        Map<String, String> requestUrlParam = new LinkedHashMap<>();
        requestUrlParam.put("code", code);
        com.alibaba.fastjson.JSONObject
                jsonObject =
                JSON.parseObject(HttpClientUtil.doPostJson(requestUrl, JSONObject.toJSONString(requestUrlParam)));
        log.info("微信获取电话号码返回结果:{}", jsonObject);
        return jsonObject;
    }

    @Override
    public CommonResult<String> getSms(String phone) throws Exception {
        int VERIFY_LENGTH = 6;
        String characters = "0123456789";
        String verify = generateRandomString(characters, VERIFY_LENGTH);
        JSONObject sendResult = SmsUtil.send(phone, Objects.requireNonNull(new JSONObject().put("code", verify)).toString());
        if (sendResult.getString("Code").equals("OK")) {
            redisUtil.setCacheObject(phone, verify, 5L, TimeUnit.MINUTES);
            return CommonResult.success("验证码发送成功");
        }
        return CommonResult.validateFailed(phone + ":" + "验证码发送失败:  " + sendResult.getString("Message"));
    }
}
